package orTree;

import predicate.*;

import java.util.List;
import java.util.Map.Entry;

import cpsc433.Pair;

public class Constraints {

	public static int getPenalities(List<Pair<Session, Lecture>> schedule,
			Lecture l, Session s, int currentPenalities) {
		int penalities = currentPenalities;

		int s1 = s1(schedule, l, s);
		int s2 = s2(schedule, l, s);
		int s3 = s3(schedule, l, s);
		int s4 = s4(schedule, l, s);
		int s5 = s5(schedule, l, s);
		int s6 = s6(schedule, l, s);
		int s7 = s7(schedule, l, s);

		penalities += s1 + s2 + s3 + s4 + s5 + s6 + s7;
		return penalities;
	}


	/*S1: FORALL s:STUDENT, c1,c2:COURSE, lec1,lec2:LECTURE | (lec1 /= lect2 \/ c1 /= c2) /\ ENROLLED(s,c1,lec1) /\ ENROLLED(s,c2,lec2) . 
        FORALL ses1,ses2:SESSION | ASSIGN(c1,lec1,ses1) /\ ASSIGN(c2,lect2,ses2) .
        EXISTS d1,d2:DAY, t1,t2,i:int | AT(ses1,d1,t1,?) /\ AT(ses2,d2,t2,?) /\ d1=d2 /\ t1<=t2 /\ EXAMLENGTH(lec1,i) . 
        t1+i < t2

        penalty=100/incident
        No student writes more than one exam in a timeslot 
	 */

	public static int s1(List<Pair<Session, Lecture>> schedule, Lecture l, Session s) {
		int penality = 100;
		int nbOfPenality = 0;
		long timeStart;
		long timeEnd;
		Day day;
			
		// For each lecture of the schedule
		for(int j=0; j< schedule.size(); j++)
		{
			// Beginning of the lecture
			timeStart = schedule.get(j).getKey().getTime();
			// End of the lecture
			timeEnd = schedule.get(j).getKey().getTime() + schedule.get(j).getKey().getLength();
			// Day
			day = schedule.get(j).getKey().getSessionDay();

			// If it is a session of the same day
			if(s.getSessionDay().equals(day)) {

				// For each student of the lecture
				for (Entry<String, Student> student : schedule.get(j).getValue().getListStudent().entrySet()){

					// For each lecture of the current student
					for (Entry<String, Lecture> lectureStudent : student.getValue().getListLecture().entrySet()) {
						
						if(l.equals(lectureStudent.getValue())) {
							// Verification if timeslots are in a same time
							if(timeStart <= s.getTime() && timeEnd > s.getTime())
								nbOfPenality++;
							else if (timeStart < s.getTime() + s.getLength() && 
									timeEnd >= s.getTime() + s.getLength())
								nbOfPenality++;
						}
					}
				}
			}
		}
		return (penality*nbOfPenality);
	}

	/*
	 * S2: FORALL s:INSTRUCTOR, c1,c2:COURSE, lec1,lec2:LECTURE | (lec1 /= lect2 \/ c1 /= c2) /\ INSTRUCTS(s,c1,lec1) /\ INSTRUCTS(s,c2,lec2) . 
        FORALL ses1,ses2:SESSION | ASSIGN(c1,lec1,ses1) /\ ASSIGN(c2,lect2,ses2) .
        EXISTS d1,d2:DAY, t1,t2,i:int | AT(ses1,d1,t1,?) /\ AT(ses2,d2,t2,?) /\ d1=d2 /\ t1<=t2 /\ EXAMLENGTH(lec1,i) . 
        (t1+i < t2) => ses1=ses2

        penalty=20/incident
        No instructor invigilates in more than one room at the same time 
	 */
	public static int s2(List<Pair<Session, Lecture>> schedule, Lecture l, Session s) {
		int penality = 20;
		int nbOfPenality = 0;
		long timeStart;
		long timeEnd;
		Day day;
		
		for(int j=0; j< schedule.size(); j++)
		{
			// Beginning of the lecture
			timeStart = schedule.get(j).getKey().getTime();
			// End of the lecture
			timeEnd = schedule.get(j).getKey().getTime() + schedule.get(j).getKey().getLength();
			// Day
			day = schedule.get(j).getKey().getSessionDay();

			// If it is a session of the same day
			if(s.getSessionDay().equals(day) &&
					!s.equals(schedule.get(j).getKey())) {

				// For the instructor of the lecture
				Instructor instructor = schedule.get(j).getValue().getLectureInstructor();	
				
				// For each lecture of the instructor
				for (Entry<String, Lecture> lectureInstructor : instructor.getListLecture().entrySet())
				{
					// Verification 
					if(l.equals(lectureInstructor.getValue())) 
					{
						// Verification if timeslots are in a same time
						if(timeStart <= s.getTime() && timeEnd >= s.getTime())
							nbOfPenality++;
						else if (timeStart <= s.getTime() + s.getLength() && 
								timeEnd >= s.getTime() + s.getLength())
							nbOfPenality++;
					}
				}				
			}
		}
		return (penality*nbOfPenality);
	}        

	/*S3: FORALL c:COURSE . 
        EXISTS1 day:Day, time:int . 
        FORALL lec:LECTURE, ses:SESSION | LECTURE(c,lec) /\ ASSIGN(c,lec,ses) .
        AT(ses,day,time,?)

        penalty=50/incident
        Every lecture for the same course should have the same exam timeslot
	 */
	public static int s3(List<Pair<Session, Lecture>> schedule, Lecture l, Session s) {
		int penality = 50;
		int nbOfPenality = 0;

		for(int j=0; j< schedule.size(); j++)
		{
			if (schedule.get(j).getValue().getCourse().equals(l.getCourse()) && (
					!schedule.get(j).getKey().getTime().equals(s.getTime()) ||
					!schedule.get(j).getKey().getLength().equals(s.getLength()) ||
					!schedule.get(j).getKey().getSessionDay().equals(s.getSessionDay())))
			{
				nbOfPenality++;
			}
		}

		return (penality*nbOfPenality);
	}

	/*S4: FORALL s:STUDENT .
        FORALL d:DAY . 
        (SUM i:int | (FORALL c:COURSE, lec:LECTURE, ses:SESSION | ENROLLED(s,c,lec) /\ ASSIGN(c,lec,ses) .       dayAssign(ses,d)) .
        EXAMLENGTH(lec,i)) <= 5

        penalty=50/incident
		No student writes for longer than 5 hours in a single day 
	 */
	public static int s4(List<Pair<Session, Lecture>> schedule, Lecture l, Session s) {
		int penality = 50;
		int nbOfPenality = 0;

		// we select the day
		Day day = s.getSessionDay();
		
		// for each student of the lecture
		for(Entry<String, Student> student : l.getListStudent().entrySet()) {
			long nbHours = 0;
			// for each lecture of the student 
			for(Entry<String, Lecture> lectureStudent : student.getValue().getListLecture().entrySet()) {
				// for each lecture of the schedule
				for(int j=0; j< schedule.size(); j++)
				{
					// if lecture of the schedule = lecture of the student
					if(lectureStudent.getValue().equals(schedule.get(j).getValue())) {
						// if it is the same day
						if(day.equals(schedule.get(j).getKey().getSessionDay())) {
							 nbHours += lectureStudent.getValue().getLength();
						}	
					}
				}
			}
			nbHours += l.getLength();
			if(nbHours > 5)
				nbOfPenality++;
		}
				
		return (penality*nbOfPenality);
	}


	/*S5: FORALL s:STUDENT, c1,c2:COURSE, lec1,lec2:LECTURE | (lec1 /= lect2 \/ c1 /= c2) /\ ENROLLED(s,c1,lec1) /\ ENROLLED(s,c2,lec2) . 
        FORALL ses1,ses2:SESSION | ASSIGN(c1,lec1,ses1) /\ ASSIGN(c2,lect2,ses2) .
        EXISTS d1,d2:DAY, t1,t2,i:int | AT(ses1,d1,t1,?) /\ AT(ses2,d2,t2,?) /\ d1=d2 /\ t1<=t2 /\ EXAMLENGTH(lec1,i) . 
        t1+i < t2+1

        penalty=50/incident
		No student should write exams with no break between them 
	 */
	public static int s5(List<Pair<Session, Lecture>> schedule, Lecture l, Session s) {
		int penality = 50;
		int nbOfPenality = 0;
		long timeStart;
		long timeEnd;
		Day day;
		long timeEndS2 = s.getTime() + s.getLength();
		long timeStartS2 = s.getTime();
		
		// For each lecture of the schedule
		for(int j=0; j< schedule.size(); j++)
		{
			// Begining of the lecture
			timeStart = schedule.get(j).getKey().getTime();
			// End of the lecture
			timeEnd = schedule.get(j).getKey().getTime() + schedule.get(j).getKey().getLength();
			// Day
			day = schedule.get(j).getKey().getSessionDay();

			// If it is a session of the same day
			if(s.getSessionDay().equals(day)) {

				// For each student of the lecture
				for (Entry<String, Student> student : schedule.get(j).getValue().getListStudent().entrySet()){

					// For each lecture of the current student
					for (Entry<String, Lecture> lectureStudent : student.getValue().getListLecture().entrySet()) {
						
						if(l.equals(lectureStudent.getValue())) {
							// Verification if timeslots are in a same time							
							
							if(timeEnd > timeEndS2) {
								if((timeStart - timeEndS2) < 1 && (timeStart - timeEndS2) >= 0) {
									nbOfPenality++;
								}
							} else if(timeEnd < timeEndS2) {
								if((timeStartS2 - timeEnd) < 1 && (timeStartS2 - timeEnd) >= 0) {
									nbOfPenality++;
								}
							}
							
						}
					}
				}
			}
		}
		
		return (penality*nbOfPenality);
	}

	/*S6: FORALL ses:SESSION, c1,c2:COURSE, lec1,lec2:LECTURE | ASSIGN(c1,lec1,ses) /\ ASSIGN(c2,lec2,ses)
        EXISTS lenght1,lenght2:int | EXAMLENGTH(c1,lec1,lenght1) /\ EXAMLENGTH(c2,lec2,lenght2).
        lenght1 = lenght2

        penalty=20/incident
		All the exams taking place in a particular session should have the same length  
	 */
	public static int s6(List<Pair<Session, Lecture>> schedule, Lecture l, Session s) {
		int penality = 20;
		int nbOfPenality = 0;

		for(int j=0; j< schedule.size(); j++)
		{
			if(schedule.get(j).getKey().getName().equals(s.getName())
					&& !schedule.get(j).getValue().getLength().equals(l.getLength()))
			{
				nbOfPenality++;
			}

		}

		return (penality*nbOfPenality);
	}

	/*S7: FORALL ses:SESSION, c:COURSE, lec:LECTURE | ASSIGN(c,lec,ses) .
        EXISTS slen, llen:int | AT(ses,?,?,slen) /\ EXAMLENGTH(c,lec,llen) .
        llen = slen

        penalty=5/incident
		Every exam in a session should take up the full time of the session  
	 */
	public static int s7(List<Pair<Session, Lecture>> schedule, Lecture l, Session s) {		
		if(!l.getLength().equals(s.getLength())) {
			return 5;
		}
		return  0;
	}
	

	public static boolean isRespectedHardConstraints(
			List<Pair<Session, Lecture>> currentSchedule, Lecture lecture,
			Session session) {
		
		boolean h3 = h3(currentSchedule, lecture, session);
		boolean h4 = h4(currentSchedule, lecture, session);

		return h3 && h4;
	}


	/**
	 * H3: the number of students writing an exam in a particular exam session
	 * may not exceed the capacity of the room FORALL ses:SESSION, r:ROOM |
	 * ROOMASSIGN(ses,r) . EXITS cap:int | CAPACITY(r,cap) . #{s:STUDENT |
	 * FORALL c:COURSE, lec: LECTURE | ASSIGN(c,lec,ses) . ENROLLED(s,c,lec)} <=
	 * cap
	 */
	public static boolean h3(List<Pair<Session, Lecture>> currentSchedule,
			Lecture lecture, Session session) {
		// Verify if there are not already a lecture associated to the session ...
		long currentNumberOfStudent = 0;

		for(int i = 0; i < currentSchedule.size(); i++) {
			// Search the same session
			if(currentSchedule.get(i).getKey().equals(session)) {				
				currentNumberOfStudent += currentSchedule.get(i).getValue().getListStudent().size();
			}       
		}
		
		long numberOfStudent = currentNumberOfStudent + lecture.getListStudent().size();
	
		long roomCapacity = session.getSessionRoom().getRoomCapacity(); 
		if(roomCapacity < numberOfStudent)
			return false;

		return true;
	}

	/**
	 * H4: every lecture's required time must be less than the session length
	 * FORALL ses:SESSION, c:COURSE, lec:LECTURE | ASSIGN(c,lec,ses) . EXISTS
	 * slen, llen:int | AT(ses,?,?,slen) /\ EXAMLENGTH(c,lec,llen) . llen <=
	 * slen
	 */
	private static boolean h4(List<Pair<Session, Lecture>> currentSchedule,
			Lecture lecture, Session session) {
		if(session.getLength() < lecture.getLength())
			return false;

		return true;
	}
}
